Data Preparation
In the preprocessing module (../notebooks/tdta_preprocessing.Rmd), we saved a tidy format data.frame containing our corpus. We’ll use that as a starting point for our work in this module.
dat_tidy <- readRDS('../data/tdta_clean_house_data_tidy.RDS')
dat_tidy
In this data.frame, words are stored in the cells of the column word. Accordingly, an entire document, identified by the unique document identifier doc_num, is stored across multiple rows. This data.frame also contains metadata like the name of the speaker associated with a given document, as well as the the speaker’s party, district, and State.
Train/Test Split
If you have enough data, there’s really never any reason to not separate your data into a training set and testing set. Of course, determing how much data is enough can be tricky, because you don’t want to create a situation where you are underpowered or unable to estimate a target parameter with sufficient precision.
However, treating documents as our units of observation, we have an $N = $ 35,959, which is certainly large enough for splitting.
doc_ids <- unique(dat_tidy$doc_num) # Get document IDs
n_docs = .50 * length(doc_ids) # Calculate number of documents to sample
set.seed(5435) # set seed for reproducibility
doc_ids_test = sample(doc_ids, n_docs) # sample document IDs for test data
dat_tidy.train <- dat_tidy %>%
filter(doc_num %!in% doc_ids_test) # Select documents for training
dat_tidy.test <- dat_tidy %>%
filter(doc_num %in% doc_ids_test) # Select documents for test
Conducting Word counts
Introduction to dictionaries
To conduct word count analyses, you need a dictionary or lexicon that specifies the words associated with your target construct(s).
To start, we’ll work with the NRC sentiment dictionary, which is one of three sentiment dictionaries packaged with tidytext:
- AFINN
- bing
- nrc
To access the NRC dictionary, we’ll use the get_sentiments function to store the NRC dictionary in an object called nrc_sent.
nrc_sent <- get_sentiments('nrc')
nrc_sent
nrc_sent contains two columns, word, which contains the words in the dictionary, and sentiment, which specifies the sentiment label associated with the word.
nrc_sent %>%
count(sentiment)
The NRC dictionary contains 10 sentiment categories and each of these categories have varying numbers of words associated with them.
Let’s take a glance at first words in each category by spreading our tidy data.frame:
nrc_sent %>%
group_by(sentiment) %>%
mutate(temp_id = row_number()) %>% # Create a temporary ID to weight top_n by
top_n(n = -50, wt=temp_id) %>% # Get first 50 items in each group
mutate(temp_id = row_number()) %>% # Create a temporary unique ID for each word in each group
ungroup() %>%
pivot_wider(names_from = sentiment, values_from = word) %>% # Spread our data
select(-temp_id)
Glancing at these words, it’s clear that words are repeated in some categories.
Question: What other characteristics stand out, if any?
A cautionary note on word count validity
One of the greatest strengths of dictionary-based text measurement methods is that they allow you to precisely define the construct you are interested in. This works extremely well when you are interested in specific words or types of words.
For example, if you are interested in function words, then it would never make sense to use anything other than a dictionary-based approach. Similarly, if you are truly interested in the usage of specific positive or negative words, then, again, it probably wouldn’t make sense to use anything other than a dictionary approach.
In these examples, there is a 1:1 relationship between the target construct and the operationalization. However, this 1:1 relationship is difficult to maintain for more abstract constructs, like “positive sentiment” or “negative sentiment”. In such cases, you (or someone else) has to decide which words evoke “positive sentiment” or “negative sentiment”.
Further, we are often interested in expressions of meaning that may operate above the word level. For instance, consider the following example:
`Let’s just say…I didn’t love it’
Most dictionary-based word count methods would estimate the sentiment expressed in this sentence as “positive” because of the token love. However, considering the entire context of this example, we can infer that the most likely sentiment is probably “negative”.
In sum, dictionary-based word count approaches can be quite powerful; however, they have two notable shortcomings:
- Dependence on dictionary validity
- Cannot account for context
This does not mean that you shouldn’t use dictionary-based word count methods. However, it does mean that you should keep these short comings in mind. And, even better, you should try to account for them through validation.
tidytext word count sentiment analysis
In principle, tidytext makes dictionary-based word count sentiment analysis quite simple.
To count the words we can just:
- Conduct an
inner_join between our data and our sentiment dictionary
- Count the matches
We’ll also divide the number of matches for each sentiment domain by the total number of words in our corpus. This will tell us the proportion associated with each sentiment domain.
total_words = nrow(dat_tidy.train)
dat_tidy.train %>%
inner_join(nrc_sent) %>%
count(sentiment) %>%
mutate(prop = n/total_words) %>%
arrange(desc(prop))
Affective sentiment by Political Party
We can also subset our data in order to ask more specific questions. For instance, we can easily estimate sentiment proportions for Democrats and Republicans.
Overall, it looks like there is very little mean sentiment variation between Republicans and Democrats. However, we’ve collapsed across documents. To get a better idea of how expressions of affective sentiment vary across Parties, let’s visualize the distribution of sentiment in documents

What if we look at the document Ns of sentiment words instead of proportions?

Question: Why might we want to look at proportion vs. N (or vice versa)?
Dynamics in affective sentiment by political party
Clearly, there isn’t much marginal between group variation in affective sentiment in this dataset. However, maybe there are interesting effects operating at other levels!
Let’s examine temporal variation in affective sentiment between parties. To do this, we will take a similar approach, but instead of counting the sentiment in each document, we’ll count the sentiment on each observed day.
First, however, let’s glance at the distribution of documents across time. For reference, let’s also add vertical lines to indicate the dates on which Clinton’s impeachment was iniated and voted on.
sent_time <- dat_tidy.train.sent %>%
filter(Party !='Independent') %>%
distinct(doc_num, .keep_all = T) %>%
count(date, Party) %>%
ggplot(aes(y = n, x = date, color=Party)) +
geom_line(alpha=.5) +
facet_wrap(Party ~. , ncol=1) +
theme_apa() +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
geom_point() +
theme_apa() +
ggtitle('N of documents across time by party') +
ylab('N') +
xlab('Date') +
labs(subtitle = "Veritical lines indicate initiation of impeachment and decision to impeach")
ggplotly(sent_time)
NA
NA
Clearly, there is substantial variation in the number of documents (i.e. speeches given by individual speakers) across time.
Question: What are our sample sizes on the impeachment-relevant days?
Now, let’s plot sentiment across time by party.

dat_tidy.train.sent %>%
filter(Party !='Independent') %>%
count(total_words, Party, date, sentiment) %>%
mutate(prop = n/total_words) %>%
ggplot(aes(y = prop, x = date, color=Party)) +
facet_wrap(sentiment~Party, ncol=4) +
scale_colour_manual(values = c("blue", "red")) +
theme() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
ggtitle('Document-level proportions of sentiment words by party') +
xlab('Party') +
ylab('N') +
geom_smooth(color='black') +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2) +
geom_point(alpha=.25)

Question: What can we learn from this figure?
Hypothesis testing with word counts
dat_tidy.train.speaker <- dat_tidy.train %>%
group_by(Party, speaker) %>%
mutate(speaker_total_words = n()) %>%
ungroup() %>%
inner_join(nrc_sent) %>%
count(Party, speaker, speaker_total_words, date, sentiment) %>%
group_by(speaker, sentiment) %>%
mutate(speaker_sent_means = mean(n),
speaker_sent_cntr = n - speaker_sent_means)
Joining, by = "word"
dat_tidy.train.speaker <- dat_tidy.train %>%
filter(Party != 'Independent') %>%
group_by(Party, speaker, date) %>%
mutate(speaker_day_n = n()) %>%
ungroup() %>%
inner_join(nrc_sent) %>%
count(Party, speaker, date, speaker_day_n, sentiment) %>%
mutate(speaker_day_sent_prop = n/speaker_day_n)
Joining, by = "word"
# Create a date range from the min/max dates in our training data
date_grid <- tibble(date = seq(min(dat_tidy.train.speaker$date),
max(dat_tidy.train.speaker$date), by='days')) %>%
mutate(date_int = row_number(), # Associate each date with an integer
date_int_scaled = date_int/100)
dat_tidy.train.speaker <- dat_tidy.train.speaker %>%
left_join(date_grid) %>%
mutate(date_int_scaled = date_int/100,
impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))
Joining, by = "date"
train.speaker.negative.m1 <- dat_tidy.train.speaker %>%
filter(sentiment=='negative') %>%
lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2 + (1 | speaker) + (1 + Party | date_int), data=.)
summary(train.speaker.negative.m1)
Linear mixed model fit by REML ['lmerMod']
Formula: speaker_day_sent_prop ~ 1 + Party * impeachment_1 + Party * impeachment_2 +
(1 | speaker) + (1 + Party | date_int)
Data: .
REML criterion at convergence: -34881.5
Scaled residuals:
Min 1Q Median 3Q Max
-2.7629 -0.6398 -0.1575 0.4602 8.0026
Random effects:
Groups Name Variance Std.Dev. Corr
speaker (Intercept) 2.464e-05 0.0049634
date_int (Intercept) 2.916e-05 0.0054003
PartyRepublican 7.967e-07 0.0008926 -0.42
Residual 2.760e-04 0.0166138
Number of obs: 6630, groups: speaker, 638; date_int, 144
Fixed effects:
Estimate Std. Error t value
(Intercept) 0.0307114 0.0006638 46.265
PartyRepublican -0.0001991 0.0006508 -0.306
impeachment_1 0.0039442 0.0058847 0.670
impeachment_2 0.0218401 0.0063649 3.431
PartyRepublican:impeachment_1 0.0004287 0.0033720 0.127
PartyRepublican:impeachment_2 -0.0002016 0.0048421 -0.042
Correlation of Fixed Effects:
(Intr) PrtyRp impc_1 impc_2 PrR:_1
PartyRpblcn -0.522
impechmnt_1 -0.081 0.026
impechmnt_2 -0.079 0.028 0.010
PrtyRpbl:_1 0.045 -0.081 -0.365 -0.007
PrtyRpbl:_2 0.036 -0.064 -0.005 -0.426 0.013
train.speaker.negative.m1.pred %>%
left_join(date_grid) %>%
ggplot(aes(x = date, y = preds, color=Party)) +
geom_line() +
geom_point(aes(y = preds), alpha=.25) +
theme_apa() +
scale_colour_manual(values = c("blue", "red")) +
theme_apa() +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
ggtitle('Daily expected proportion of negative language for an average speaker' ) +
ylab('Speaker proportion negative language') +
xlab('Date')
Joining, by = c("date_int", "date", "date_int_scaled")

Disgust
train.speaker.disgust.m1 <- dat_tidy.train.speaker %>%
filter(sentiment=='disgust') %>%
lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2 + (1 | speaker) + (1 + Party | date_int), data=.)
summary(train.speaker.disgust.m1)
Linear mixed model fit by REML ['lmerMod']
Formula: speaker_day_sent_prop ~ 1 + Party * impeachment_1 + Party * impeachment_2 +
(1 | speaker) + (1 + Party | date_int)
Data: .
REML criterion at convergence: -37281.3
Scaled residuals:
Min 1Q Median 3Q Max
-2.1327 -0.6168 -0.2363 0.3417 11.8113
Random effects:
Groups Name Variance Std.Dev. Corr
speaker (Intercept) 3.769e-06 0.0019413
date_int (Intercept) 4.166e-06 0.0020410
PartyRepublican 1.502e-07 0.0003876 0.15
Residual 5.562e-05 0.0074581
Number of obs: 5428, groups: speaker, 576; date_int, 141
Fixed effects:
Estimate Std. Error t value
(Intercept) 9.484e-03 2.782e-04 34.086
PartyRepublican 3.560e-04 2.948e-04 1.207
impeachment_1 1.546e-03 2.342e-03 0.660
impeachment_2 6.181e-03 2.556e-03 2.419
PartyRepublican:impeachment_1 2.194e-04 1.651e-03 0.133
PartyRepublican:impeachment_2 -3.549e-05 2.214e-03 -0.016
Correlation of Fixed Effects:
(Intr) PrtyRp impc_1 impc_2 PrR:_1
PartyRpblcn -0.516
impechmnt_1 -0.085 0.029
impechmnt_2 -0.085 0.034 0.011
PrtyRpbl:_1 0.043 -0.088 -0.301 -0.008
PrtyRpbl:_2 0.041 -0.078 -0.006 -0.391 0.014
train.speaker.disgust.m1.pred <- dat_tidy.train.speaker.pred_grid %>%
mutate(preds = predict(train.speaker.disgust.m1, newdata=dat_tidy.train.speaker.pred_grid, allow.new.levels=T))
train.speaker.disgust.m1.pred %>%
left_join(date_grid) %>%
ggplot(aes(x = date, y = preds, color=Party)) +
geom_line() +
geom_point(aes(y = preds), alpha=.25) +
theme_apa() +
scale_colour_manual(values = c("blue", "red")) +
theme_apa() +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
ylab('Speaker proportion disgust language') +
xlab('Date')
Joining, by = c("date_int", "date", "date_int_scaled")

train.speaker.all.m1 <- dat_tidy.train.speaker %>%
lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2 + (1 | speaker) + (1 + Party | date_int) + (1 + impeachment_1 + impeachment_2 | sentiment), data=.)
summary(train.speaker.all.m1)
dat_tidy.train.speaker.pred_grid.all_sent <- expand.grid(Party = unique(dat_tidy.train.speaker$Party),
speaker = 'new_speaker',
date_int = unique(dat_tidy.train.speaker$date_int),
sentiment=unique(dat_tidy.train.speaker$sentiment))
dat_tidy.train.speaker.pred_grid.all_sent <- dat_tidy.train.speaker.pred_grid.all_sent %>%
left_join(date_grid) %>%
mutate(impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))
train.speaker.all.m1.pred <- dat_tidy.train.speaker.pred_grid.all_sent %>%
mutate(preds = predict(train.speaker.all.m1,
newdata=dat_tidy.train.speaker.pred_grid.all_sent,
allow.new.levels=T))
train.speaker.all.m1.pred %>%
left_join(date_grid) %>%
ggplot(aes(x = date, y = preds, color=Party)) +
geom_line() +
geom_point(aes(y = preds), alpha=.25) +
theme_apa() +
scale_colour_manual(values = c("blue", "red")) +
theme_apa() +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
ylab('Speaker proportion disgust language') +
xlab('Date') + facet_wrap(sentiment~., ncol=2)
Joining, by = c("date_int", "date", "date_int_scaled")

train.speaker.all.m1.pred %>%
left_join(date_grid) %>%
ggplot(aes(x = date, y = preds, color=Party)) +
geom_line() +
geom_point(aes(y = preds), alpha=.25) +
theme_apa() +
scale_colour_manual(values = c("blue", "red")) +
theme_apa() +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
ylab('Speaker proportion disgust language') +
xlab('Date') + facet_wrap(sentiment~., ncol=2, scales='free_y')
Joining, by = c("date_int", "date", "date_int_scaled")

sjPlot::plot_model(train.speaker.all.m1, type='re')[3]
[[1]]

Confirmation
dat_tidy.test.speaker <- dat_tidy.test %>%
group_by(Party, speaker) %>%
mutate(speaker_total_words = n()) %>%
ungroup() %>%
inner_join(nrc_sent) %>%
count(Party, speaker, speaker_total_words, date, sentiment) %>%
group_by(speaker, sentiment)
Joining, by = "word"
dat_tidy.test.speaker <- dat_tidy.test %>%
filter(Party != 'Independent') %>%
group_by(Party, speaker, date) %>%
mutate(speaker_day_n = n()) %>%
ungroup() %>%
inner_join(nrc_sent) %>%
count(Party, speaker, date, speaker_day_n, sentiment) %>%
mutate(speaker_day_sent_prop = n/speaker_day_n)
Joining, by = "word"
# Create a date range from the min/max dates in our testing data
date_grid <- tibble(date = seq(min(dat_tidy.test.speaker$date),
max(dat_tidy.test.speaker$date), by='days')) %>%
mutate(date_int = row_number(), # Associate each date with an integer
date_int_scaled = date_int/100)
dat_tidy.test.speaker <- dat_tidy.test.speaker %>%
left_join(date_grid) %>%
mutate(date_int_scaled = date_int/100,
impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))
Joining, by = "date"
test.speaker.negative.m1 <- dat_tidy.test.speaker %>%
filter(sentiment=='negative') %>%
lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2 + (1 | speaker) + (1 + Party | date_int), data=.)
boundary (singular) fit: see ?isSingular
summary(test.speaker.negative.m1)
Linear mixed model fit by REML ['lmerMod']
Formula:
speaker_day_sent_prop ~ 1 + Party * impeachment_1 + Party * impeachment_2 +
(1 | speaker) + (1 + Party | date_int)
Data: .
REML criterion at convergence: -34917.8
Scaled residuals:
Min 1Q Median 3Q Max
-2.3963 -0.6268 -0.1663 0.4381 11.4546
Random effects:
Groups Name Variance Std.Dev. Corr
speaker (Intercept) 2.261e-05 0.0047553
date_int (Intercept) 3.451e-05 0.0058743
PartyRepublican 6.270e-07 0.0007919 -1.00
Residual 3.021e-04 0.0173800
Number of obs: 6743, groups: speaker, 652; date_int, 143
Fixed effects:
Estimate Std. Error t value
(Intercept) 0.0313529 0.0006947 45.134
PartyRepublican -0.0002498 0.0006483 -0.385
impeachment_1 0.0090151 0.0063993 1.409
impeachment_2 0.0173639 0.0070045 2.479
PartyRepublican:impeachment_1 -0.0083012 0.0033936 -2.446
PartyRepublican:impeachment_2 0.0058701 0.0053485 1.098
Correlation of Fixed Effects:
(Intr) PrtyRp impc_1 impc_2 PrR:_1
PartyRpblcn -0.546
impechmnt_1 -0.083 0.032
impechmnt_2 -0.078 0.031 0.009
PrtyRpbl:_1 0.056 -0.090 -0.500 -0.008
PrtyRpbl:_2 0.038 -0.062 -0.005 -0.506 0.014
convergence code: 0
boundary (singular) fit: see ?isSingular
dat_tidy.test.speaker.pred_grid <- expand.grid(Party = unique(dat_tidy.test.speaker$Party),
speaker = 'new_speaker',
date_int = unique(dat_tidy.test.speaker$date_int))
dat_tidy.test.speaker.pred_grid <- dat_tidy.test.speaker.pred_grid %>%
left_join(date_grid) %>%
mutate(impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))
Joining, by = "date_int"
test.speaker.negative.m1.pred <- dat_tidy.test.speaker.pred_grid %>%
mutate(preds = predict(test.speaker.negative.m1, newdata=dat_tidy.test.speaker.pred_grid, allow.new.levels=T))
test.speaker.negative.m1.pred %>%
left_join(date_grid) %>%
ggplot(aes(x = date, y = preds, color=Party)) +
geom_line() +
geom_point(aes(y = preds), alpha=.25) +
theme_apa() +
scale_colour_manual(values = c("blue", "red")) +
theme_apa() +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
ggtitle('Daily expected proportion of negative language for an average speaker' ) +
ylab('Speaker proportion negative language') +
xlab('Date')
Joining, by = c("date_int", "date", "date_int_scaled")

NA
Disgust
test.speaker.disgust.m1 <- dat_tidy.test.speaker %>%
filter(sentiment=='disgust') %>%
lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2 + (1 | speaker) + (1 + Party | date_int), data=.)
boundary (singular) fit: see ?isSingular
summary(test.speaker.disgust.m1)
Linear mixed model fit by REML ['lmerMod']
Formula:
speaker_day_sent_prop ~ 1 + Party * impeachment_1 + Party * impeachment_2 +
(1 | speaker) + (1 + Party | date_int)
Data: .
REML criterion at convergence: -37469.3
Scaled residuals:
Min 1Q Median 3Q Max
-2.7009 -0.5983 -0.2280 0.3244 11.5548
Random effects:
Groups Name Variance Std.Dev. Corr
speaker (Intercept) 5.295e-06 0.002301
date_int (Intercept) 4.316e-06 0.002078
PartyRepublican 1.797e-08 0.000134 -1.00
Residual 5.818e-05 0.007628
Number of obs: 5497, groups: speaker, 570; date_int, 143
Fixed effects:
Estimate Std. Error t value
(Intercept) 0.0100217 0.0002945 34.027
PartyRepublican -0.0002112 0.0003215 -0.657
impeachment_1 0.0029204 0.0024163 1.209
impeachment_2 0.0050296 0.0027192 1.850
PartyRepublican:impeachment_1 -0.0014567 0.0016049 -0.908
PartyRepublican:impeachment_2 0.0026842 0.0023911 1.123
Correlation of Fixed Effects:
(Intr) PrtyRp impc_1 impc_2 PrR:_1
PartyRpblcn -0.566
impechmnt_1 -0.082 0.032
impechmnt_2 -0.074 0.030 0.010
PrtyRpbl:_1 0.053 -0.089 -0.456 -0.008
PrtyRpbl:_2 0.037 -0.065 -0.006 -0.511 0.014
convergence code: 0
boundary (singular) fit: see ?isSingular
test.speaker.disgust.m1.pred <- dat_tidy.test.speaker.pred_grid %>%
mutate(preds = predict(test.speaker.disgust.m1, newdata=dat_tidy.test.speaker.pred_grid, allow.new.levels=T))
test.speaker.disgust.m1.pred %>%
left_join(date_grid) %>%
ggplot(aes(x = date, y = preds, color=Party)) +
geom_line() +
geom_point(aes(y = preds), alpha=.25) +
theme_apa() +
scale_colour_manual(values = c("blue", "red")) +
theme_apa() +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
ylab('Speaker proportion disgust language') +
xlab('Date')
Joining, by = c("date_int", "date", "date_int_scaled")

dat_tidy.test.speaker.pred_grid.all_sent <- dat_tidy.test.speaker.pred_grid.all_sent %>%
left_join(date_grid) %>%
mutate(impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))
Joining, by = "date_int"
test.speaker.all.m1.pred %>%
left_join(date_grid) %>%
ggplot(aes(x = date, y = preds, color=Party)) +
geom_line() +
geom_point(aes(y = preds), alpha=.25) +
theme_apa() +
scale_colour_manual(values = c("blue", "red")) +
theme_apa() +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
ylab('Speaker proportion disgust language') +
xlab('Date') + facet_wrap(sentiment~., ncol=2)
Joining, by = c("date_int", "date", "date_int_scaled")

test.speaker.all.m1.pred %>%
left_join(date_grid) %>%
ggplot(aes(x = date, y = preds, color=Party)) +
geom_line() +
geom_point(aes(y = preds), alpha=.25) +
theme_apa() +
scale_colour_manual(values = c("blue", "red")) +
theme_apa() +
geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
ylab('Speaker proportion disgust language') +
xlab('Date') + facet_wrap(sentiment~., ncol=2, scales='free_y')
Joining, by = c("date_int", "date", "date_int_scaled")

sjPlot::plot_model(test.speaker.all.m1, type='re')[3]
[[1]]

Validating dictionaries
One of the greatest strengths of dictionary-based text measurement methods is that they allow you to precisely define the construct you are interested in. This works extremely well when you are interested in specific words or types of words.
For example, if you are interested in function words, then it would never make sense to use anything other than a dictionary-based approach. Similarly, if you are truly interested in the usage of positive or negative words, then, again, it probably wouldn’t make sense to use anything other than a dictionary approach.
However,
LS0tCnRpdGxlOiAiVGhlb3J5IERyaXZlbiBUZXh0IEFuYWx5c2lzIFdvcmtzaG9wIgpzdWJ0aXRsZTogIkRpY3Rpb25hcnkgTWV0aG9kcyAtLS0gV29yZCBjb3VudHMgXG5cblNQU1AgMjAyMCIKYXV0aG9yOiAKICBuYW1lOiAiSm9lIEhvb3ZlciAmIEJyZW5kYW4gS2VubmVkeSIKICBlbWFpbDogImpvc2VwaC5ob292ZXJAa2VsbG9nZy5ub3J0aHdlc3Rlcm4uZWR1XG5cbmJ0a2VubmVkQHVzYy5lZHUiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKLS0tCgoKIyBPdmVydmlldwoKSW4gdGhpcyBtb2R1bGUsIHdlIHdpbGwgZXhwbG9yZSBkaWN0aW9uYXJ5LWJhc2VkIHdvcmQgY291bnQgYXBwcm9hY2hlcyB0ZXh0IGFuYWx5c2lzIG1lYXN1cmVtZW50LiAKCldlIHdpbGwgYmUgd29ya2luZyB3aXRoIFUuUy4gSG91c2UgZmxvb3Igc3BlZWNoZXMgc3Bhbm5pbmcgSnVuZSAxOTk4IHRocm91Z2ggSnVseSAxOTk5LiBNb3JlIHNwZWNpZmljYWxseSwgd2Ugd2lsbDoKCigxKSBpbnZlc3RpZ2F0ZSB2YXJpYXRpb25zIGluIHNlbnRpbWVudCBhcyBhIGZ1bmN0aW9uIG9mIHBvbGl0aWNhbCBwYXJ0eQooMikgZXhwbG9yZSBzZW50aW1lbnQgZHluYW1pY3MgaW4gdGhlIGNvbnRleHQgb2YgUHJlc2lkZW50IEJpbGwgQ2xpbnRvbidzIGltcGVhY2htZW50LiAKCgojIEVudmlyb25tZW50IFByZXBhcmF0aW9uCmBgYHtyLCBlY2hvPUYsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQojIERlZmluZSBjaHVuayBvcHRpb25zCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvPVQsIG1lc3NhZ2U9Riwgd2FybmluZz1GKQpgYGAKCmBgYHtyLCBtZXNzYWdlPUYsICBlY2hvPVR9CiMgTG9hZCBwYWNrYWdlcyAKbGlicmFyeShwYWNtYW4pCnBfbG9hZChyZWFkciwgZHBseXIsIHRpZHlyLCBnZ3Bsb3QyLCBqdG9vbHMsIAogICAgICAga25pdHIsIHJlc2hhcGUyLCBqc29ubGl0ZSwgbHVicmlkYXRlKQoKYGBgCgojIERhdGEgUHJlcGFyYXRpb24gCgpJbiB0aGUgcHJlcHJvY2Vzc2luZyBtb2R1bGUgKGAuLi9ub3RlYm9va3MvdGR0YV9wcmVwcm9jZXNzaW5nLlJtZGApLCB3ZSBzYXZlZCBhIHRpZHkgZm9ybWF0IGRhdGEuZnJhbWUgY29udGFpbmluZyBvdXIgY29ycHVzLiBXZSdsbCB1c2UgdGhhdCBhcyBhIHN0YXJ0aW5nIHBvaW50IGZvciBvdXIgd29yayBpbiB0aGlzIG1vZHVsZS4KCmBgYHtyfQoKZGF0X3RpZHkgPC0gcmVhZFJEUygnLi4vZGF0YS90ZHRhX2NsZWFuX2hvdXNlX2RhdGFfdGlkeS5SRFMnKQoKZGF0X3RpZHkKCmBgYAoKCkluIHRoaXMgZGF0YS5mcmFtZSwgd29yZHMgYXJlIHN0b3JlZCBpbiB0aGUgY2VsbHMgb2YgdGhlIGNvbHVtbiBgd29yZGAuIEFjY29yZGluZ2x5LCBhbiBlbnRpcmUgZG9jdW1lbnQsIGlkZW50aWZpZWQgYnkgdGhlIHVuaXF1ZSBkb2N1bWVudCBpZGVudGlmaWVyIGBkb2NfbnVtYCwgaXMgc3RvcmVkIGFjcm9zcyBtdWx0aXBsZSByb3dzLiBUaGlzIGRhdGEuZnJhbWUgYWxzbyBjb250YWlucyBtZXRhZGF0YSBsaWtlIHRoZSBuYW1lIG9mIHRoZSBzcGVha2VyIGFzc29jaWF0ZWQgd2l0aCBhIGdpdmVuIGRvY3VtZW50LCBhcyB3ZWxsIGFzIHRoZSB0aGUgc3BlYWtlcidzIHBhcnR5LCBkaXN0cmljdCwgYW5kIFN0YXRlLgoKCiMjIFRyYWluL1Rlc3QgU3BsaXQgCgpJZiB5b3UgaGF2ZSBlbm91Z2ggZGF0YSwgdGhlcmUncyByZWFsbHkgX25ldmVyXyBhbnkgcmVhc29uIHRvIG5vdCBzZXBhcmF0ZSB5b3VyIGRhdGEgaW50byBhIHRyYWluaW5nIHNldCBhbmQgdGVzdGluZyBzZXQuIE9mIGNvdXJzZSwgZGV0ZXJtaW5nIGhvdyBtdWNoIGRhdGEgaXMgKmVub3VnaCogY2FuIGJlIHRyaWNreSwgYmVjYXVzZSB5b3UgZG9uJ3Qgd2FudCB0byBjcmVhdGUgYSBzaXR1YXRpb24gd2hlcmUgeW91IGFyZSB1bmRlcnBvd2VyZWQgb3IgdW5hYmxlIHRvIGVzdGltYXRlIGEgdGFyZ2V0IHBhcmFtZXRlciB3aXRoIHN1ZmZpY2llbnQgcHJlY2lzaW9uLiAKCkhvd2V2ZXIsIHRyZWF0aW5nIGRvY3VtZW50cyBhcyBvdXIgdW5pdHMgb2Ygb2JzZXJ2YXRpb24sIHdlIGhhdmUgYW4gJE4gPSAkIDM1LDk1OSwgd2hpY2ggaXMgY2VydGFpbmx5IGxhcmdlIGVub3VnaCBmb3Igc3BsaXR0aW5nLgoKYGBge3J9Cgpkb2NfaWRzIDwtIHVuaXF1ZShkYXRfdGlkeSRkb2NfbnVtKSAjIEdldCBkb2N1bWVudCBJRHMKCm5fZG9jcyA9IC41MCAqIGxlbmd0aChkb2NfaWRzKSAjIENhbGN1bGF0ZSBudW1iZXIgb2YgZG9jdW1lbnRzIHRvIHNhbXBsZQoKc2V0LnNlZWQoNTQzNSkgIyBzZXQgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5Cgpkb2NfaWRzX3Rlc3QgPSBzYW1wbGUoZG9jX2lkcywgbl9kb2NzKSAjIHNhbXBsZSBkb2N1bWVudCBJRHMgZm9yIHRlc3QgZGF0YQoKZGF0X3RpZHkudHJhaW4gPC0gZGF0X3RpZHkgJT4lCiAgZmlsdGVyKGRvY19udW0gJSFpbiUgZG9jX2lkc190ZXN0KSAjIFNlbGVjdCBkb2N1bWVudHMgZm9yIHRyYWluaW5nCgpkYXRfdGlkeS50ZXN0IDwtIGRhdF90aWR5ICU+JQogIGZpbHRlcihkb2NfbnVtICVpbiUgZG9jX2lkc190ZXN0KSAjIFNlbGVjdCBkb2N1bWVudHMgZm9yIHRlc3QKCgpgYGAKCgoKIyMgQ29uZHVjdGluZyBXb3JkIGNvdW50cwoKIyMjIEludHJvZHVjdGlvbiB0byBkaWN0aW9uYXJpZXMKClRvIGNvbmR1Y3Qgd29yZCBjb3VudCBhbmFseXNlcywgeW91IG5lZWQgYSAqZGljdGlvbmFyeSogb3IgKmxleGljb24qIHRoYXQgc3BlY2lmaWVzIHRoZSB3b3JkcyBhc3NvY2lhdGVkIHdpdGggeW91ciB0YXJnZXQgY29uc3RydWN0KHMpLiAKClRvIHN0YXJ0LCB3ZSdsbCB3b3JrIHdpdGggdGhlIE5SQyBzZW50aW1lbnQgZGljdGlvbmFyeSwgd2hpY2ggaXMgb25lIG9mIHRocmVlIHNlbnRpbWVudCBkaWN0aW9uYXJpZXMgcGFja2FnZWQgd2l0aCBgdGlkeXRleHRgOiAKCjEuIEFGSU5OCjIuIGJpbmcKMy4gbnJjCgpUbyBhY2Nlc3MgdGhlIE5SQyBkaWN0aW9uYXJ5LCB3ZSdsbCB1c2UgdGhlIGBnZXRfc2VudGltZW50c2AgZnVuY3Rpb24gdG8gc3RvcmUgdGhlIE5SQyBkaWN0aW9uYXJ5IGluIGFuIG9iamVjdCBjYWxsZWQgYG5yY19zZW50YC4KCmBgYHtyfQpucmNfc2VudCA8LSBnZXRfc2VudGltZW50cygnbnJjJykKbnJjX3NlbnQKYGBgCgpgbnJjX3NlbnRgIGNvbnRhaW5zIHR3byBjb2x1bW5zLCBgd29yZGAsIHdoaWNoIGNvbnRhaW5zIHRoZSB3b3JkcyBpbiB0aGUgZGljdGlvbmFyeSwgYW5kIGBzZW50aW1lbnRgLCB3aGljaCBzcGVjaWZpZXMgdGhlIHNlbnRpbWVudCBsYWJlbCBhc3NvY2lhdGVkIHdpdGggdGhlIHdvcmQuIAoKYGBge3J9Cm5yY19zZW50ICU+JQogIGNvdW50KHNlbnRpbWVudCkKYGBgCgpUaGUgTlJDIGRpY3Rpb25hcnkgY29udGFpbnMgMTAgc2VudGltZW50IGNhdGVnb3JpZXMgYW5kIGVhY2ggb2YgdGhlc2UgY2F0ZWdvcmllcyBoYXZlIHZhcnlpbmcgbnVtYmVycyBvZiB3b3JkcyBhc3NvY2lhdGVkIHdpdGggdGhlbS4gCgpMZXQncyB0YWtlIGEgZ2xhbmNlIGF0IGZpcnN0IHdvcmRzIGluIGVhY2ggY2F0ZWdvcnkgYnkgc3ByZWFkaW5nIG91ciB0aWR5IGRhdGEuZnJhbWU6CgpgYGB7cn0KbnJjX3NlbnQgJT4lCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUgCiAgbXV0YXRlKHRlbXBfaWQgPSByb3dfbnVtYmVyKCkpICU+JSAjIENyZWF0ZSBhIHRlbXBvcmFyeSBJRCB0byB3ZWlnaHQgdG9wX24gYnkKICB0b3BfbihuID0gLTUwLCB3dD10ZW1wX2lkKSAlPiUgICAgICMgR2V0IGZpcnN0IDUwIGl0ZW1zIGluIGVhY2ggZ3JvdXAgCiAgbXV0YXRlKHRlbXBfaWQgPSByb3dfbnVtYmVyKCkpICU+JSAjIENyZWF0ZSBhIHRlbXBvcmFyeSB1bmlxdWUgSUQgZm9yIGVhY2ggd29yZCBpbiBlYWNoIGdyb3VwCiAgdW5ncm91cCgpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzZW50aW1lbnQsIHZhbHVlc19mcm9tID0gd29yZCkgJT4lICMgU3ByZWFkIG91ciBkYXRhCiAgc2VsZWN0KC10ZW1wX2lkKQpgYGAKCgpHbGFuY2luZyBhdCB0aGVzZSB3b3JkcywgaXQncyBjbGVhciB0aGF0IHdvcmRzIGFyZSByZXBlYXRlZCBpbiBzb21lIGNhdGVnb3JpZXMuIAoKCjxicj4KCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBXaGF0IG90aGVyIGNoYXJhY3RlcmlzdGljcyBzdGFuZCBvdXQsIGlmIGFueT8KPC9kaXY+CgojIyMgQSBjYXV0aW9uYXJ5IG5vdGUgb24gd29yZCBjb3VudCB2YWxpZGl0eQoKT25lIG9mIHRoZSBncmVhdGVzdCBzdHJlbmd0aHMgb2YgZGljdGlvbmFyeS1iYXNlZCB0ZXh0IG1lYXN1cmVtZW50IG1ldGhvZHMgaXMgdGhhdCB0aGV5IGFsbG93IHlvdSB0byBwcmVjaXNlbHkgZGVmaW5lIHRoZSBjb25zdHJ1Y3QgeW91IGFyZSBpbnRlcmVzdGVkIGluLiBUaGlzIHdvcmtzIGV4dHJlbWVseSB3ZWxsIHdoZW4geW91IGFyZSBpbnRlcmVzdGVkIGluIHNwZWNpZmljIHdvcmRzIG9yIHR5cGVzIG9mIHdvcmRzLiAKCkZvciBleGFtcGxlLCBpZiB5b3UgYXJlIGludGVyZXN0ZWQgaW4gKmZ1bmN0aW9uIHdvcmRzKiwgdGhlbiBpdCB3b3VsZCBuZXZlciBtYWtlIHNlbnNlIHRvIHVzZSBhbnl0aGluZyBvdGhlciB0aGFuIGEgZGljdGlvbmFyeS1iYXNlZCBhcHByb2FjaC4gU2ltaWxhcmx5LCBpZiB5b3UgYXJlIHRydWx5IGludGVyZXN0ZWQgaW4gdGhlIHVzYWdlIG9mIHNwZWNpZmljICpwb3NpdGl2ZSogb3IgKm5lZ2F0aXZlKiB3b3JkcywgdGhlbiwgYWdhaW4sIGl0IHByb2JhYmx5IHdvdWxkbid0IG1ha2Ugc2Vuc2UgdG8gdXNlIGFueXRoaW5nIG90aGVyIHRoYW4gYSBkaWN0aW9uYXJ5IGFwcHJvYWNoLiAKCkluIHRoZXNlIGV4YW1wbGVzLCB0aGVyZSBpcyBhIDE6MSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgdGFyZ2V0IGNvbnN0cnVjdCBhbmQgdGhlIG9wZXJhdGlvbmFsaXphdGlvbi4gSG93ZXZlciwgdGhpcyAxOjEgcmVsYXRpb25zaGlwIGlzIGRpZmZpY3VsdCB0byBtYWludGFpbiBmb3IgbW9yZSBhYnN0cmFjdCBjb25zdHJ1Y3RzLCBsaWtlICJwb3NpdGl2ZSBzZW50aW1lbnQiIG9yICJuZWdhdGl2ZSBzZW50aW1lbnQiLiBJbiBzdWNoIGNhc2VzLCB5b3UgKG9yIHNvbWVvbmUgZWxzZSkgaGFzIHRvIGRlY2lkZSB3aGljaCB3b3JkcyBldm9rZSAicG9zaXRpdmUgc2VudGltZW50IiBvciAibmVnYXRpdmUgc2VudGltZW50Ii4gCgpGdXJ0aGVyLCB3ZSBhcmUgb2Z0ZW4gaW50ZXJlc3RlZCBpbiBleHByZXNzaW9ucyBvZiBtZWFuaW5nIHRoYXQgbWF5IG9wZXJhdGUgYWJvdmUgdGhlIHdvcmQgbGV2ZWwuIEZvciBpbnN0YW5jZSwgY29uc2lkZXIgdGhlIGZvbGxvd2luZyBleGFtcGxlOiAKCmBMZXQncyBqdXN0IHNheS4uLkkgZGlkbid0IGxvdmUgaXQnCgpNb3N0IGRpY3Rpb25hcnktYmFzZWQgd29yZCBjb3VudCBtZXRob2RzIHdvdWxkIGVzdGltYXRlIHRoZSBzZW50aW1lbnQgZXhwcmVzc2VkIGluIHRoaXMgc2VudGVuY2UgYXMgInBvc2l0aXZlIiBiZWNhdXNlIG9mIHRoZSB0b2tlbiBgbG92ZWAuIEhvd2V2ZXIsIGNvbnNpZGVyaW5nIHRoZSBlbnRpcmUgKmNvbnRleHQqIG9mIHRoaXMgZXhhbXBsZSwgd2UgY2FuIGluZmVyIHRoYXQgdGhlIG1vc3QgbGlrZWx5IHNlbnRpbWVudCBpcyBwcm9iYWJseSAibmVnYXRpdmUiLiAKCkluIHN1bSwgZGljdGlvbmFyeS1iYXNlZCB3b3JkIGNvdW50IGFwcHJvYWNoZXMgY2FuIGJlIHF1aXRlIHBvd2VyZnVsOyBob3dldmVyLCB0aGV5IGhhdmUgdHdvIG5vdGFibGUgc2hvcnRjb21pbmdzOiAKCjEuIERlcGVuZGVuY2Ugb24gZGljdGlvbmFyeSB2YWxpZGl0eQoyLiBDYW5ub3QgYWNjb3VudCBmb3IgY29udGV4dAoKVGhpcyAqZG9lcyBub3QqIG1lYW4gdGhhdCB5b3Ugc2hvdWxkbid0IHVzZSBkaWN0aW9uYXJ5LWJhc2VkIHdvcmQgY291bnQgbWV0aG9kcy4gSG93ZXZlciwgaXQgKmRvZXMqIG1lYW4gdGhhdCB5b3Ugc2hvdWxkIGtlZXAgdGhlc2Ugc2hvcnQgY29taW5ncyBpbiBtaW5kLiBBbmQsIGV2ZW4gYmV0dGVyLCB5b3Ugc2hvdWxkIHRyeSB0byBhY2NvdW50IGZvciB0aGVtIHRocm91Z2ggdmFsaWRhdGlvbi4gCgojIyMgYHRpZHl0ZXh0YCB3b3JkIGNvdW50IHNlbnRpbWVudCBhbmFseXNpcwoKSW4gcHJpbmNpcGxlLCBgdGlkeXRleHRgIG1ha2VzIGRpY3Rpb25hcnktYmFzZWQgd29yZCBjb3VudCBzZW50aW1lbnQgYW5hbHlzaXMgcXVpdGUgc2ltcGxlLiAKClRvIGNvdW50IHRoZSB3b3JkcyB3ZSBjYW4ganVzdDogCgoxLiBDb25kdWN0IGFuIGBpbm5lcl9qb2luYCBiZXR3ZWVuIG91ciBkYXRhIGFuZCBvdXIgc2VudGltZW50IGRpY3Rpb25hcnkKMi4gQ291bnQgdGhlIG1hdGNoZXMKCldlJ2xsIGFsc28gZGl2aWRlIHRoZSBudW1iZXIgb2YgbWF0Y2hlcyBmb3IgZWFjaCBzZW50aW1lbnQgZG9tYWluIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2Ygd29yZHMgaW4gb3VyIGNvcnB1cy4gVGhpcyB3aWxsIHRlbGwgdXMgdGhlIHByb3BvcnRpb24gYXNzb2NpYXRlZCB3aXRoIGVhY2ggc2VudGltZW50IGRvbWFpbi4KCmBgYHtyfQoKdG90YWxfd29yZHMgPSBucm93KGRhdF90aWR5LnRyYWluKQoKZGF0X3RpZHkudHJhaW4gJT4lCiAgaW5uZXJfam9pbihucmNfc2VudCkgJT4lCiAgY291bnQoc2VudGltZW50KSAlPiUKICBtdXRhdGUocHJvcCA9IG4vdG90YWxfd29yZHMpICU+JQogIGFycmFuZ2UoZGVzYyhwcm9wKSkKCmBgYAoKCiMjIyBBZmZlY3RpdmUgc2VudGltZW50IGJ5IFBvbGl0aWNhbCBQYXJ0eQoKV2UgY2FuIGFsc28gc3Vic2V0IG91ciBkYXRhIGluIG9yZGVyIHRvIGFzayBtb3JlIHNwZWNpZmljIHF1ZXN0aW9ucy4gRm9yIGluc3RhbmNlLCB3ZSBjYW4gZWFzaWx5IGVzdGltYXRlIHNlbnRpbWVudCBwcm9wb3J0aW9ucyBmb3IgRGVtb2NyYXRzIGFuZCBSZXB1YmxpY2Fucy4KCmBgYHtyfQoKZGF0X3RpZHkudHJhaW4uc2VudCA8LSBkYXRfdGlkeS50cmFpbiAlPiUKICBncm91cF9ieShQYXJ0eSkgJT4lICMgR3JvdXAgYnkgUGFydHkKICBtdXRhdGUodG90YWxfd29yZHMgPSBuKCkpICU+JSAjIENhbGN1bGF0ZSB0aGUgdG90YWwgd29yZHMgaW4gZWFjaCBncm91cAogIHVuZ3JvdXAoKSAlPiUgCiAgaW5uZXJfam9pbihucmNfc2VudCkgIyBEcm9wIHdvcmRzIHRoYXQgYXJlbid0IGluIHNlbnRpbWVudCBkaWN0aW9uYXJ5CiAgCmRhdF90aWR5LnRyYWluLnNlbnQgJT4lIGNvdW50KHRvdGFsX3dvcmRzLCBQYXJ0eSwgc2VudGltZW50KSAlPiUgIyBDb3VudCB0aGUgbnVtYmVyIG9mIHJvd3MgaW4gZWFjaCBQYXJ0eSBmb3IgZWFjaCBzZW50aW1lbnQKICBtdXRhdGUocHJvcCA9IG4vdG90YWxfd29yZHMpICU+JSAjIENhbGN1bGF0ZSB0aGUgcHJvcG9ydGlvbgogIGFycmFuZ2UoZGVzYyhwcm9wKSkgJT4lICMgQXJyYW5nZSBpbiBkZXNjZW5kaW5nIG9yZGVyIGJ5IHByb3BvcnRpb24gcG9zaXRpdmUKICBzZWxlY3QoLW4sIC10b3RhbF93b3JkcykgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT0nUGFydHknLCB2YWx1ZXNfZnJvbSA9ICdwcm9wJykKCgoKYGBgCgoKT3ZlcmFsbCwgaXQgbG9va3MgbGlrZSB0aGVyZSBpcyB2ZXJ5IGxpdHRsZSBtZWFuIHNlbnRpbWVudCB2YXJpYXRpb24gYmV0d2VlbiBSZXB1YmxpY2FucyBhbmQgRGVtb2NyYXRzLiBIb3dldmVyLCB3ZSd2ZSBjb2xsYXBzZWQgYWNyb3NzIGRvY3VtZW50cy4gVG8gZ2V0IGEgYmV0dGVyIGlkZWEgb2YgaG93IGV4cHJlc3Npb25zIG9mIGFmZmVjdGl2ZSBzZW50aW1lbnQgdmFyeSBhY3Jvc3MgUGFydGllcywgbGV0J3MgdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2Ygc2VudGltZW50IGluIGRvY3VtZW50cyAKCgoKYGBge3J9CiAKZGF0X3RpZHkudHJhaW4uc2VudCAlPiUKICBmaWx0ZXIoUGFydHkgIT0nSW5kZXBlbmRlbnQnKSAlPiUKICBjb3VudCh0b3RhbF93b3JkcywgUGFydHksIGRvY19udW0sIHNlbnRpbWVudCkgJT4lCiAgbXV0YXRlKHByb3AgPSBuL3RvdGFsX3dvcmRzKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSBwcm9wLCB4ID0gUGFydHksIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2ppdHRlcihhbHBoYT0uMjUpICsgCiAgZmFjZXRfd3JhcCgufnNlbnRpbWVudCwgbmNvbD01KSArIAogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiYmx1ZSIsICJyZWQiKSkgKwogIHRoZW1lX2FwYSgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICBnZ3RpdGxlKCdEb2N1bWVudC1sZXZlbCBwcm9wb3J0aW9ucyBvZiBzZW50aW1lbnQgd29yZHMgYnkgcGFydHknKSArIAogIHhsYWIoJ1BhcnR5JykgKyAKICB5bGFiKCdQcm9wb3J0aW9uJykKCmBgYAoKCgpXaGF0IGlmIHdlIGxvb2sgYXQgdGhlIGRvY3VtZW50ICpOKnMgb2Ygc2VudGltZW50IHdvcmRzIGluc3RlYWQgb2YgcHJvcG9ydGlvbnM/CgpgYGB7cn0KIApkYXRfdGlkeS50cmFpbi5zZW50ICU+JQogIGZpbHRlcihQYXJ0eSAhPSdJbmRlcGVuZGVudCcpICU+JQogIGNvdW50KHRvdGFsX3dvcmRzLCBQYXJ0eSwgZG9jX251bSwgc2VudGltZW50KSAlPiUKICBtdXRhdGUocHJvcCA9IG4vdG90YWxfd29yZHMpICU+JQogIGdncGxvdChhZXMoeSA9IG4sIHggPSBQYXJ0eSwgY29sb3I9UGFydHkpKSArIAogIGdlb21faml0dGVyKGFscGhhPS4yNSkgKyAKICBmYWNldF93cmFwKC5+c2VudGltZW50LCBuY29sPTUpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArCiAgdGhlbWUoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgZ2d0aXRsZSgnRG9jdW1lbnQtbGV2ZWwgTnMgb2Ygc2VudGltZW50IHdvcmRzIGJ5IHBhcnR5JykgKyAKICB4bGFiKCdQYXJ0eScpICsgCiAgeWxhYignTicpCgpgYGAKCgoKPGJyPgoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtc3VjY2VzcyIgcm9sZT0iYWxlcnQiPgogIDxzdHJvbmc+UXVlc3Rpb246PC9zdHJvbmc+IFdoeSBtaWdodCB3ZSB3YW50IHRvIGxvb2sgYXQgcHJvcG9ydGlvbiB2cy4gTiAob3IgdmljZSB2ZXJzYSk/CjwvZGl2PgoKCiMjIyBEeW5hbWljcyBpbiBhZmZlY3RpdmUgc2VudGltZW50IGJ5IHBvbGl0aWNhbCBwYXJ0eQoKQ2xlYXJseSwgdGhlcmUgaXNuJ3QgbXVjaCBtYXJnaW5hbCBiZXR3ZWVuIGdyb3VwIHZhcmlhdGlvbiBpbiBhZmZlY3RpdmUgc2VudGltZW50IGluIHRoaXMgZGF0YXNldC4gSG93ZXZlciwgbWF5YmUgdGhlcmUgYXJlIGludGVyZXN0aW5nIGVmZmVjdHMgb3BlcmF0aW5nIGF0IG90aGVyIGxldmVscyEKCkxldCdzIGV4YW1pbmUgdGVtcG9yYWwgdmFyaWF0aW9uIGluIGFmZmVjdGl2ZSBzZW50aW1lbnQgYmV0d2VlbiBwYXJ0aWVzLiBUbyBkbyB0aGlzLCB3ZSB3aWxsIHRha2UgYSBzaW1pbGFyIGFwcHJvYWNoLCBidXQgaW5zdGVhZCBvZiBjb3VudGluZyB0aGUgc2VudGltZW50IGluIGVhY2ggZG9jdW1lbnQsIHdlJ2xsIGNvdW50IHRoZSBzZW50aW1lbnQgb24gZWFjaCBvYnNlcnZlZCBkYXkuIAoKRmlyc3QsIGhvd2V2ZXIsIGxldCdzIGdsYW5jZSBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGRvY3VtZW50cyBhY3Jvc3MgdGltZS4gRm9yIHJlZmVyZW5jZSwgbGV0J3MgYWxzbyBhZGQgdmVydGljYWwgbGluZXMgdG8gaW5kaWNhdGUgdGhlIGRhdGVzIG9uIHdoaWNoIENsaW50b24ncyBpbXBlYWNobWVudCB3YXMgaW5pYXRlZCBhbmQgdm90ZWQgb24uCgoKCmBgYHtyLCBmaWcud2lkdGg9MTB9CgpzZW50X3RpbWUgPC0gZGF0X3RpZHkudHJhaW4uc2VudCAlPiUKICBmaWx0ZXIoUGFydHkgIT0nSW5kZXBlbmRlbnQnKSAlPiUKICBkaXN0aW5jdChkb2NfbnVtLCAua2VlcF9hbGwgPSBUKSAlPiUKICBjb3VudChkYXRlLCBQYXJ0eSkgJT4lCiAgZ2dwbG90KGFlcyh5ID0gbiwgeCA9IGRhdGUsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoYWxwaGE9LjUpICsgCiAgZmFjZXRfd3JhcChQYXJ0eSB+LiAsIG5jb2w9MSkgKwogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfYXBhKCkgKwogIGdndGl0bGUoJ04gb2YgZG9jdW1lbnRzIGFjcm9zcyB0aW1lIGJ5IHBhcnR5JykgKwogIHlsYWIoJ04nKSArIAogIHhsYWIoJ0RhdGUnKQoKZ2dwbG90bHkoc2VudF90aW1lKQoKCmBgYAoKQ2xlYXJseSwgdGhlcmUgaXMgc3Vic3RhbnRpYWwgdmFyaWF0aW9uIGluIHRoZSBudW1iZXIgb2YgZG9jdW1lbnRzIChpLmUuIHNwZWVjaGVzIGdpdmVuIGJ5IGluZGl2aWR1YWwgc3BlYWtlcnMpIGFjcm9zcyB0aW1lLiAKCgo8YnI+Cgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1zdWNjZXNzIiByb2xlPSJhbGVydCI+CiAgPHN0cm9uZz5RdWVzdGlvbjo8L3N0cm9uZz4gV2hhdCBhcmUgb3VyIHNhbXBsZSBzaXplcyBvbiB0aGUgaW1wZWFjaG1lbnQtcmVsZXZhbnQgZGF5cz8KPC9kaXY+CgoKCk5vdywgbGV0J3MgcGxvdCBzZW50aW1lbnQgYWNyb3NzIHRpbWUgYnkgcGFydHkuIAoKYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0KCmRhdF90aWR5LnRyYWluLnNlbnQgJT4lCiAgZmlsdGVyKFBhcnR5ICE9J0luZGVwZW5kZW50JykgJT4lCiAgY291bnQodG90YWxfd29yZHMsIFBhcnR5LCBkYXRlLCBzZW50aW1lbnQpICU+JQogIG11dGF0ZShwcm9wID0gbi90b3RhbF93b3JkcykgJT4lCiAgZ2dwbG90KGFlcyh5ID0gbiwgeCA9IGRhdGUsIGNvbG9yPVBhcnR5KSkgKyAKICBmYWNldF93cmFwKHNlbnRpbWVudH5QYXJ0eSwgbmNvbD00KSArIAogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiYmx1ZSIsICJyZWQiKSkgKwogIHRoZW1lKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoJ0RvY3VtZW50LWxldmVsIE5zIG9mIHNlbnRpbWVudCB3b3JkcyBieSBwYXJ0eScpICsgCiAgeGxhYignUGFydHknKSArIAogIHlsYWIoJ04nKSArIAogIGdlb21fc21vb3RoKGNvbG9yPSdibGFjaycpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JykpLCBsaW5ldHlwZT0yKSArCiAgZ2VvbV9wb2ludChhbHBoYT0uMjUpIAoKYGBgCgoKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9CgpkYXRfdGlkeS50cmFpbi5zZW50ICU+JQogIGZpbHRlcihQYXJ0eSAhPSdJbmRlcGVuZGVudCcpICU+JQogIGNvdW50KHRvdGFsX3dvcmRzLCBQYXJ0eSwgZGF0ZSwgc2VudGltZW50KSAlPiUKICBtdXRhdGUocHJvcCA9IG4vdG90YWxfd29yZHMpICU+JQogIGdncGxvdChhZXMoeSA9IHByb3AsIHggPSBkYXRlLCBjb2xvcj1QYXJ0eSkpICsgCiAgZmFjZXRfd3JhcChzZW50aW1lbnR+UGFydHksIG5jb2w9NCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsKICB0aGVtZSgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICBnZ3RpdGxlKCdEb2N1bWVudC1sZXZlbCBwcm9wb3J0aW9ucyBvZiBzZW50aW1lbnQgd29yZHMgYnkgcGFydHknKSArIAogIHhsYWIoJ1BhcnR5JykgKyAKICB5bGFiKCdOJykgKyAKICBnZW9tX3Ntb290aChjb2xvcj0nYmxhY2snKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTAtMDgnKSksIGxpbmV0eXBlPTIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MikgKwogIGdlb21fcG9pbnQoYWxwaGE9LjI1KSAKCmBgYAoKCjxicj4KCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBXaGF0IGNhbiB3ZSBsZWFybiBmcm9tIHRoaXMgZmlndXJlPwo8L2Rpdj4KCiMjIEh5cG90aGVzaXMgdGVzdGluZyB3aXRoIHdvcmQgY291bnRzCgpgYGB7cn0KCmRhdF90aWR5LnRyYWluLnNwZWFrZXIgPC0gZGF0X3RpZHkudHJhaW4gJT4lCiAgZ3JvdXBfYnkoUGFydHksIHNwZWFrZXIpICU+JQogIG11dGF0ZShzcGVha2VyX3RvdGFsX3dvcmRzID0gbigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgaW5uZXJfam9pbihucmNfc2VudCkgJT4lCiAgY291bnQoUGFydHksIHNwZWFrZXIsIHNwZWFrZXJfdG90YWxfd29yZHMsIGRhdGUsIHNlbnRpbWVudCkgJT4lCiAgZ3JvdXBfYnkoc3BlYWtlciwgc2VudGltZW50KSAlPiUKICBtdXRhdGUoc3BlYWtlcl9zZW50X21lYW5zID0gbWVhbihuKSwgCiAgICAgICAgIHNwZWFrZXJfc2VudF9jbnRyID0gbiAtIHNwZWFrZXJfc2VudF9tZWFucykKICAKYGBgCgoKCmBgYHtyfQoKZGF0X3RpZHkudHJhaW4uc3BlYWtlciA8LSBkYXRfdGlkeS50cmFpbiAlPiUKICBmaWx0ZXIoUGFydHkgIT0gJ0luZGVwZW5kZW50JykgJT4lCiAgZ3JvdXBfYnkoUGFydHksIHNwZWFrZXIsIGRhdGUpICU+JQogIG11dGF0ZShzcGVha2VyX2RheV9uID0gbigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgaW5uZXJfam9pbihucmNfc2VudCkgJT4lCiAgY291bnQoUGFydHksIHNwZWFrZXIsIGRhdGUsIHNwZWFrZXJfZGF5X24sIHNlbnRpbWVudCkgJT4lCiAgbXV0YXRlKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCA9IG4vc3BlYWtlcl9kYXlfbikKCgoKCiMgQ3JlYXRlIGEgZGF0ZSByYW5nZSBmcm9tIHRoZSBtaW4vbWF4IGRhdGVzIGluIG91ciB0cmFpbmluZyBkYXRhCmRhdGVfZ3JpZCA8LSB0aWJibGUoZGF0ZSA9IHNlcShtaW4oZGF0X3RpZHkudHJhaW4uc3BlYWtlciRkYXRlKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXgoZGF0X3RpZHkudHJhaW4uc3BlYWtlciRkYXRlKSwgYnk9J2RheXMnKSkgJT4lCiAgbXV0YXRlKGRhdGVfaW50ID0gcm93X251bWJlcigpLCAjIEFzc29jaWF0ZSBlYWNoIGRhdGUgd2l0aCBhbiBpbnRlZ2VyIAogICAgICAgICBkYXRlX2ludF9zY2FsZWQgPSBkYXRlX2ludC8xMDApCgpkYXRfdGlkeS50cmFpbi5zcGVha2VyIDwtIGRhdF90aWR5LnRyYWluLnNwZWFrZXIgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGRhdGVfaW50X3NjYWxlZCA9IGRhdGVfaW50LzEwMCwgCiAgICAgICAgIGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQogIApgYGAKCgpgYGB7cn0KCnRyYWluLnNwZWFrZXIubmVnYXRpdmUubTEgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlciAlPiUKICBmaWx0ZXIoc2VudGltZW50PT0nbmVnYXRpdmUnKSAlPiUKICBsbWVyKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCB+IDEgKyBQYXJ0eSppbXBlYWNobWVudF8xICsgUGFydHkqaW1wZWFjaG1lbnRfMiAgKyAoMSAgfCBzcGVha2VyKSArICgxICsgUGFydHkgfCBkYXRlX2ludCksIGRhdGE9LikKCnN1bW1hcnkodHJhaW4uc3BlYWtlci5uZWdhdGl2ZS5tMSkKCmBgYAoKCgpgYGB7cn0KCgpkYXRfdGlkeS50cmFpbi5zcGVha2VyLnByZWRfZ3JpZCA8LSBleHBhbmQuZ3JpZChQYXJ0eSA9IHVuaXF1ZShkYXRfdGlkeS50cmFpbi5zcGVha2VyJFBhcnR5KSwgCiAgICAgICAgICAgIHNwZWFrZXIgPSAnbmV3X3NwZWFrZXInLCAKICAgICAgICAgICAgZGF0ZV9pbnQgPSB1bmlxdWUoZGF0X3RpZHkudHJhaW4uc3BlYWtlciRkYXRlX2ludCkpCgoKZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQoKCgp0cmFpbi5zcGVha2VyLm5lZ2F0aXZlLm0xLnByZWQgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQgJT4lCiAgbXV0YXRlKHByZWRzID0gcHJlZGljdCh0cmFpbi5zcGVha2VyLm5lZ2F0aXZlLm0xLCBuZXdkYXRhPWRhdF90aWR5LnRyYWluLnNwZWFrZXIucHJlZF9ncmlkLCBhbGxvdy5uZXcubGV2ZWxzPVQpKQogIAoKCnRyYWluLnNwZWFrZXIubmVnYXRpdmUubTEucHJlZCAlPiUKICBsZWZ0X2pvaW4oZGF0ZV9ncmlkKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gcHJlZHMsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSBwcmVkcyksIGFscGhhPS4yNSkgKwogIHRoZW1lX2FwYSgpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArIAogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdndGl0bGUoJ0RhaWx5IGV4cGVjdGVkIHByb3BvcnRpb24gb2YgbmVnYXRpdmUgbGFuZ3VhZ2UgZm9yIGFuIGF2ZXJhZ2Ugc3BlYWtlcicgKSArCiAgeWxhYignU3BlYWtlciBwcm9wb3J0aW9uIG5lZ2F0aXZlIGxhbmd1YWdlJykgKyAKICB4bGFiKCdEYXRlJykKCiAgCmBgYAoKCgojIyBEaXNndXN0IAoKYGBge3J9CgoKdHJhaW4uc3BlYWtlci5kaXNndXN0Lm0xIDwtIGRhdF90aWR5LnRyYWluLnNwZWFrZXIgJT4lCiAgZmlsdGVyKHNlbnRpbWVudD09J2Rpc2d1c3QnKSAlPiUKICBsbWVyKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCB+IDEgKyBQYXJ0eSppbXBlYWNobWVudF8xICsgUGFydHkqaW1wZWFjaG1lbnRfMiAgKyAoMSAgfCBzcGVha2VyKSArICgxICsgUGFydHkgfCBkYXRlX2ludCksIGRhdGE9LikKCnN1bW1hcnkodHJhaW4uc3BlYWtlci5kaXNndXN0Lm0xKQoKCgp0cmFpbi5zcGVha2VyLmRpc2d1c3QubTEucHJlZCA8LSBkYXRfdGlkeS50cmFpbi5zcGVha2VyLnByZWRfZ3JpZCAlPiUKICBtdXRhdGUocHJlZHMgPSBwcmVkaWN0KHRyYWluLnNwZWFrZXIuZGlzZ3VzdC5tMSwgbmV3ZGF0YT1kYXRfdGlkeS50cmFpbi5zcGVha2VyLnByZWRfZ3JpZCwgYWxsb3cubmV3LmxldmVscz1UKSkKICAKCgp0cmFpbi5zcGVha2VyLmRpc2d1c3QubTEucHJlZCAlPiUKICBsZWZ0X2pvaW4oZGF0ZV9ncmlkKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gcHJlZHMsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSBwcmVkcyksIGFscGhhPS4yNSkgKwogIHRoZW1lX2FwYSgpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArIAogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdndGl0bGUoJ0RhaWx5IGV4cGVjdGVkIHByb3BvcnRpb24gb2YgZGlzZ3VzdCBsYW5ndWFnZSBmb3IgYW4gYXZlcmFnZSBzcGVha2VyJyApICsKICB5bGFiKCdTcGVha2VyIHByb3BvcnRpb24gZGlzZ3VzdCBsYW5ndWFnZScpICsgCiAgeGxhYignRGF0ZScpCgoKYGBgCgoKCmBgYHtyfQoKCgp0cmFpbi5zcGVha2VyLmFsbC5tMSA8LSBkYXRfdGlkeS50cmFpbi5zcGVha2VyICU+JQogIGxtZXIoc3BlYWtlcl9kYXlfc2VudF9wcm9wIH4gMSArIFBhcnR5KmltcGVhY2htZW50XzEgKyBQYXJ0eSppbXBlYWNobWVudF8yICArICgxIHwgc3BlYWtlcikgKyAoMSArIFBhcnR5IHwgZGF0ZV9pbnQpICsgKDEgKyBpbXBlYWNobWVudF8xICArIGltcGVhY2htZW50XzIgfCBzZW50aW1lbnQpLCBkYXRhPS4pCgpzdW1tYXJ5KHRyYWluLnNwZWFrZXIuYWxsLm0xKQoKCmRhdF90aWR5LnRyYWluLnNwZWFrZXIucHJlZF9ncmlkLmFsbF9zZW50IDwtIGV4cGFuZC5ncmlkKFBhcnR5ID0gdW5pcXVlKGRhdF90aWR5LnRyYWluLnNwZWFrZXIkUGFydHkpLCAKICAgICAgICAgICAgc3BlYWtlciA9ICduZXdfc3BlYWtlcicsIAogICAgICAgICAgICBkYXRlX2ludCA9IHVuaXF1ZShkYXRfdGlkeS50cmFpbi5zcGVha2VyJGRhdGVfaW50KSwKICAgICAgICAgICAgc2VudGltZW50PXVuaXF1ZShkYXRfdGlkeS50cmFpbi5zcGVha2VyJHNlbnRpbWVudCkpCgoKZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQoKCnRyYWluLnNwZWFrZXIuYWxsLm0xLnByZWQgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgJT4lCiAgbXV0YXRlKHByZWRzID0gcHJlZGljdCh0cmFpbi5zcGVha2VyLmFsbC5tMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhPWRhdF90aWR5LnRyYWluLnNwZWFrZXIucHJlZF9ncmlkLmFsbF9zZW50LCAKICAgICAgICAgICAgICAgICAgICAgICAgIGFsbG93Lm5ldy5sZXZlbHM9VCkpCiAgCgpgYGAKCgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQp0cmFpbi5zcGVha2VyLmFsbC5tMS5wcmVkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBwcmVkcywgY29sb3I9UGFydHkpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludChhZXMoeSA9IHByZWRzKSwgYWxwaGE9LjI1KSArCiAgdGhlbWVfYXBhKCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsgCiAgdGhlbWVfYXBhKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEwLTA4JykpLCBsaW5ldHlwZT0yLCBhbHBoYT0uMjUpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2d0aXRsZSgnRGFpbHkgZXhwZWN0ZWQgcHJvcG9ydGlvbiBvZiBkaXNndXN0IGxhbmd1YWdlIGZvciBhbiBhdmVyYWdlIHNwZWFrZXInICkgKwogIHlsYWIoJ1NwZWFrZXIgcHJvcG9ydGlvbiBkaXNndXN0IGxhbmd1YWdlJykgKyAKICB4bGFiKCdEYXRlJykgKyBmYWNldF93cmFwKHNlbnRpbWVudH4uLCBuY29sPTIpCgpgYGAKCgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQp0cmFpbi5zcGVha2VyLmFsbC5tMS5wcmVkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBwcmVkcywgY29sb3I9UGFydHkpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludChhZXMoeSA9IHByZWRzKSwgYWxwaGE9LjI1KSArCiAgdGhlbWVfYXBhKCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsgCiAgdGhlbWVfYXBhKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEwLTA4JykpLCBsaW5ldHlwZT0yLCBhbHBoYT0uMjUpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2d0aXRsZSgnRGFpbHkgZXhwZWN0ZWQgcHJvcG9ydGlvbiBvZiBkaXNndXN0IGxhbmd1YWdlIGZvciBhbiBhdmVyYWdlIHNwZWFrZXInICkgKwogIHlsYWIoJ1NwZWFrZXIgcHJvcG9ydGlvbiBkaXNndXN0IGxhbmd1YWdlJykgKyAKICB4bGFiKCdEYXRlJykgKyBmYWNldF93cmFwKHNlbnRpbWVudH4uLCBuY29sPTIsIHNjYWxlcz0nZnJlZV95JykKCgpgYGAKCgoKYGBge3J9CnNqUGxvdDo6cGxvdF9tb2RlbCh0cmFpbi5zcGVha2VyLmFsbC5tMSwgdHlwZT0ncmUnKVszXQpgYGAKCgojIyBDb25maXJtYXRpb24gCgoKYGBge3J9CgpkYXRfdGlkeS50ZXN0LnNwZWFrZXIgPC0gZGF0X3RpZHkudGVzdCAlPiUKICBncm91cF9ieShQYXJ0eSwgc3BlYWtlcikgJT4lCiAgbXV0YXRlKHNwZWFrZXJfdG90YWxfd29yZHMgPSBuKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBpbm5lcl9qb2luKG5yY19zZW50KSAlPiUKICBjb3VudChQYXJ0eSwgc3BlYWtlciwgc3BlYWtlcl90b3RhbF93b3JkcywgZGF0ZSwgc2VudGltZW50KSAlPiUKICBncm91cF9ieShzcGVha2VyLCBzZW50aW1lbnQpCiAgCmBgYAoKCgpgYGB7cn0KCmRhdF90aWR5LnRlc3Quc3BlYWtlciA8LSBkYXRfdGlkeS50ZXN0ICU+JQogIGZpbHRlcihQYXJ0eSAhPSAnSW5kZXBlbmRlbnQnKSAlPiUKICBncm91cF9ieShQYXJ0eSwgc3BlYWtlciwgZGF0ZSkgJT4lCiAgbXV0YXRlKHNwZWFrZXJfZGF5X24gPSBuKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBpbm5lcl9qb2luKG5yY19zZW50KSAlPiUKICBjb3VudChQYXJ0eSwgc3BlYWtlciwgZGF0ZSwgc3BlYWtlcl9kYXlfbiwgc2VudGltZW50KSAlPiUKICBtdXRhdGUoc3BlYWtlcl9kYXlfc2VudF9wcm9wID0gbi9zcGVha2VyX2RheV9uKQoKCgoKIyBDcmVhdGUgYSBkYXRlIHJhbmdlIGZyb20gdGhlIG1pbi9tYXggZGF0ZXMgaW4gb3VyIHRlc3RpbmcgZGF0YQpkYXRlX2dyaWQgPC0gdGliYmxlKGRhdGUgPSBzZXEobWluKGRhdF90aWR5LnRlc3Quc3BlYWtlciRkYXRlKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXgoZGF0X3RpZHkudGVzdC5zcGVha2VyJGRhdGUpLCBieT0nZGF5cycpKSAlPiUKICBtdXRhdGUoZGF0ZV9pbnQgPSByb3dfbnVtYmVyKCksICMgQXNzb2NpYXRlIGVhY2ggZGF0ZSB3aXRoIGFuIGludGVnZXIgCiAgICAgICAgIGRhdGVfaW50X3NjYWxlZCA9IGRhdGVfaW50LzEwMCkKCmRhdF90aWR5LnRlc3Quc3BlYWtlciA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGRhdGVfaW50X3NjYWxlZCA9IGRhdGVfaW50LzEwMCwgCiAgICAgICAgIGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQogIApgYGAKCgpgYGB7cn0KCnRlc3Quc3BlYWtlci5uZWdhdGl2ZS5tMSA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIgJT4lCiAgZmlsdGVyKHNlbnRpbWVudD09J25lZ2F0aXZlJykgJT4lCiAgbG1lcihzcGVha2VyX2RheV9zZW50X3Byb3AgfiAxICsgUGFydHkqaW1wZWFjaG1lbnRfMSArIFBhcnR5KmltcGVhY2htZW50XzIgICsgKDEgIHwgc3BlYWtlcikgKyAoMSArIFBhcnR5IHwgZGF0ZV9pbnQpLCBkYXRhPS4pCgpzdW1tYXJ5KHRlc3Quc3BlYWtlci5uZWdhdGl2ZS5tMSkKCmBgYAoKCgpgYGB7cn0KCgpkYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkIDwtIGV4cGFuZC5ncmlkKFBhcnR5ID0gdW5pcXVlKGRhdF90aWR5LnRlc3Quc3BlYWtlciRQYXJ0eSksIAogICAgICAgICAgICBzcGVha2VyID0gJ25ld19zcGVha2VyJywgCiAgICAgICAgICAgIGRhdGVfaW50ID0gdW5pcXVlKGRhdF90aWR5LnRlc3Quc3BlYWtlciRkYXRlX2ludCkpCgoKZGF0X3RpZHkudGVzdC5zcGVha2VyLnByZWRfZ3JpZCA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIG11dGF0ZShpbXBlYWNobWVudF8xID0gaWZlbHNlKGRhdGUgPT0gYXNfZGF0ZXRpbWUoJzE5OTgtMTAtMDgnKSwgMSwgMCksCiAgICAgICAgIGltcGVhY2htZW50XzIgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMi0xOScpLCAxLCAwKSkKCgoKdGVzdC5zcGVha2VyLm5lZ2F0aXZlLm0xLnByZWQgPC0gZGF0X3RpZHkudGVzdC5zcGVha2VyLnByZWRfZ3JpZCAlPiUKICBtdXRhdGUocHJlZHMgPSBwcmVkaWN0KHRlc3Quc3BlYWtlci5uZWdhdGl2ZS5tMSwgbmV3ZGF0YT1kYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkLCBhbGxvdy5uZXcubGV2ZWxzPVQpKQogIAoKCnRlc3Quc3BlYWtlci5uZWdhdGl2ZS5tMS5wcmVkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBwcmVkcywgY29sb3I9UGFydHkpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludChhZXMoeSA9IHByZWRzKSwgYWxwaGE9LjI1KSArCiAgdGhlbWVfYXBhKCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsgCiAgdGhlbWVfYXBhKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEwLTA4JykpLCBsaW5ldHlwZT0yLCBhbHBoYT0uMjUpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2d0aXRsZSgnRGFpbHkgZXhwZWN0ZWQgcHJvcG9ydGlvbiBvZiBuZWdhdGl2ZSBsYW5ndWFnZSBmb3IgYW4gYXZlcmFnZSBzcGVha2VyJyApICsKICB5bGFiKCdTcGVha2VyIHByb3BvcnRpb24gbmVnYXRpdmUgbGFuZ3VhZ2UnKSArIAogIHhsYWIoJ0RhdGUnKQoKICAKYGBgCgoKCiMjIERpc2d1c3QgCgpgYGB7cn0KCgp0ZXN0LnNwZWFrZXIuZGlzZ3VzdC5tMSA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIgJT4lCiAgZmlsdGVyKHNlbnRpbWVudD09J2Rpc2d1c3QnKSAlPiUKICBsbWVyKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCB+IDEgKyBQYXJ0eSppbXBlYWNobWVudF8xICsgUGFydHkqaW1wZWFjaG1lbnRfMiAgKyAoMSAgfCBzcGVha2VyKSArICgxICsgUGFydHkgfCBkYXRlX2ludCksIGRhdGE9LikKCnN1bW1hcnkodGVzdC5zcGVha2VyLmRpc2d1c3QubTEpCgoKCnRlc3Quc3BlYWtlci5kaXNndXN0Lm0xLnByZWQgPC0gZGF0X3RpZHkudGVzdC5zcGVha2VyLnByZWRfZ3JpZCAlPiUKICBtdXRhdGUocHJlZHMgPSBwcmVkaWN0KHRlc3Quc3BlYWtlci5kaXNndXN0Lm0xLCBuZXdkYXRhPWRhdF90aWR5LnRlc3Quc3BlYWtlci5wcmVkX2dyaWQsIGFsbG93Lm5ldy5sZXZlbHM9VCkpCiAgCgoKdGVzdC5zcGVha2VyLmRpc2d1c3QubTEucHJlZCAlPiUKICBsZWZ0X2pvaW4oZGF0ZV9ncmlkKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gcHJlZHMsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSBwcmVkcyksIGFscGhhPS4yNSkgKwogIHRoZW1lX2FwYSgpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArIAogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdndGl0bGUoJ0RhaWx5IGV4cGVjdGVkIHByb3BvcnRpb24gb2YgZGlzZ3VzdCBsYW5ndWFnZSBmb3IgYW4gYXZlcmFnZSBzcGVha2VyJyApICsKICB5bGFiKCdTcGVha2VyIHByb3BvcnRpb24gZGlzZ3VzdCBsYW5ndWFnZScpICsgCiAgeGxhYignRGF0ZScpCgoKYGBgCgoKCmBgYHtyfQoKCgp0ZXN0LnNwZWFrZXIuYWxsLm0xIDwtIGRhdF90aWR5LnRlc3Quc3BlYWtlciAlPiUKICBsbWVyKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCB+IDEgKyBQYXJ0eSppbXBlYWNobWVudF8xICsgUGFydHkqaW1wZWFjaG1lbnRfMiAgKyAoMSB8IHNwZWFrZXIpICsgKDEgKyBQYXJ0eSB8IGRhdGVfaW50KSArICgxICsgaW1wZWFjaG1lbnRfMSAgKyBpbXBlYWNobWVudF8yIHwgc2VudGltZW50KSwgZGF0YT0uKQoKc3VtbWFyeSh0ZXN0LnNwZWFrZXIuYWxsLm0xKQoKCmRhdF90aWR5LnRlc3Quc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgPC0gZXhwYW5kLmdyaWQoUGFydHkgPSB1bmlxdWUoZGF0X3RpZHkudGVzdC5zcGVha2VyJFBhcnR5KSwgCiAgICAgICAgICAgIHNwZWFrZXIgPSAnbmV3X3NwZWFrZXInLCAKICAgICAgICAgICAgZGF0ZV9pbnQgPSB1bmlxdWUoZGF0X3RpZHkudGVzdC5zcGVha2VyJGRhdGVfaW50KSwKICAgICAgICAgICAgc2VudGltZW50PXVuaXF1ZShkYXRfdGlkeS50ZXN0LnNwZWFrZXIkc2VudGltZW50KSkKCgpkYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkLmFsbF9zZW50IDwtIGRhdF90aWR5LnRlc3Quc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQoKCnRlc3Quc3BlYWtlci5hbGwubTEucHJlZCA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkLmFsbF9zZW50ICU+JQogIG11dGF0ZShwcmVkcyA9IHByZWRpY3QodGVzdC5zcGVha2VyLmFsbC5tMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhPWRhdF90aWR5LnRlc3Quc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQsIAogICAgICAgICAgICAgICAgICAgICAgICAgYWxsb3cubmV3LmxldmVscz1UKSkKICAKCmBgYAoKCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9CnRlc3Quc3BlYWtlci5hbGwubTEucHJlZCAlPiUKICBsZWZ0X2pvaW4oZGF0ZV9ncmlkKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gcHJlZHMsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSBwcmVkcyksIGFscGhhPS4yNSkgKwogIHRoZW1lX2FwYSgpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArIAogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdndGl0bGUoJ0RhaWx5IGV4cGVjdGVkIHByb3BvcnRpb24gb2YgZGlzZ3VzdCBsYW5ndWFnZSBmb3IgYW4gYXZlcmFnZSBzcGVha2VyJyApICsKICB5bGFiKCdTcGVha2VyIHByb3BvcnRpb24gZGlzZ3VzdCBsYW5ndWFnZScpICsgCiAgeGxhYignRGF0ZScpICsgZmFjZXRfd3JhcChzZW50aW1lbnR+LiwgbmNvbD0yKQoKYGBgCgoKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9Nn0KdGVzdC5zcGVha2VyLmFsbC5tMS5wcmVkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBwcmVkcywgY29sb3I9UGFydHkpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludChhZXMoeSA9IHByZWRzKSwgYWxwaGE9LjI1KSArCiAgdGhlbWVfYXBhKCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsgCiAgdGhlbWVfYXBhKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEwLTA4JykpLCBsaW5ldHlwZT0yLCBhbHBoYT0uMjUpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2d0aXRsZSgnRGFpbHkgZXhwZWN0ZWQgcHJvcG9ydGlvbiBvZiBkaXNndXN0IGxhbmd1YWdlIGZvciBhbiBhdmVyYWdlIHNwZWFrZXInICkgKwogIHlsYWIoJ1NwZWFrZXIgcHJvcG9ydGlvbiBkaXNndXN0IGxhbmd1YWdlJykgKyAKICB4bGFiKCdEYXRlJykgKyBmYWNldF93cmFwKHNlbnRpbWVudH4uLCBuY29sPTIsIHNjYWxlcz0nZnJlZV95JykKCgpgYGAKCgoKYGBge3J9CnNqUGxvdDo6cGxvdF9tb2RlbCh0ZXN0LnNwZWFrZXIuYWxsLm0xLCB0eXBlPSdyZScpWzNdCmBgYAoKCgojIyMgVmFsaWRhdGluZyBkaWN0aW9uYXJpZXMgCgpPbmUgb2YgdGhlIGdyZWF0ZXN0IHN0cmVuZ3RocyBvZiBkaWN0aW9uYXJ5LWJhc2VkIHRleHQgbWVhc3VyZW1lbnQgbWV0aG9kcyBpcyB0aGF0IHRoZXkgYWxsb3cgeW91IHRvIHByZWNpc2VseSBkZWZpbmUgdGhlIGNvbnN0cnVjdCB5b3UgYXJlIGludGVyZXN0ZWQgaW4uIFRoaXMgd29ya3MgZXh0cmVtZWx5IHdlbGwgd2hlbiB5b3UgYXJlIGludGVyZXN0ZWQgaW4gc3BlY2lmaWMgd29yZHMgb3IgdHlwZXMgb2Ygd29yZHMuIAoKRm9yIGV4YW1wbGUsIGlmIHlvdSBhcmUgaW50ZXJlc3RlZCBpbiAqZnVuY3Rpb24gd29yZHMqLCB0aGVuIGl0IHdvdWxkIG5ldmVyIG1ha2Ugc2Vuc2UgdG8gdXNlIGFueXRoaW5nIG90aGVyIHRoYW4gYSBkaWN0aW9uYXJ5LWJhc2VkIGFwcHJvYWNoLiBTaW1pbGFybHksIGlmIHlvdSBhcmUgdHJ1bHkgaW50ZXJlc3RlZCBpbiB0aGUgdXNhZ2Ugb2YgKnBvc2l0aXZlKiBvciAqbmVnYXRpdmUqIHdvcmRzLCB0aGVuLCBhZ2FpbiwgaXQgcHJvYmFibHkgd291bGRuJ3QgbWFrZSBzZW5zZSB0byB1c2UgYW55dGhpbmcgb3RoZXIgdGhhbiBhIGRpY3Rpb25hcnkgYXBwcm9hY2guIAoKSG93ZXZlciwgCg==